/**
 * Logback: the reliable, generic, fast and flexible logging framework.
 * Copyright (C) 1999-2013, QOS.ch. All rights reserved.
 *
 * This program and the accompanying materials are dual-licensed under
 * either the terms of the Eclipse Public License v1.0 as published by
 * the Eclipse Foundation
 *
 *   or (per the licensee's choosing)
 *
 * under the terms of the GNU Lesser General Public License version 2.1
 * as published by the Free Software Foundation.
 */
package ch.qos.logback.classic;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.slf4j.helpers.MarkerIgnoringBase;

import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.CoreConstants;


public class HLogger extends MarkerIgnoringBase {

  private static final long serialVersionUID = 1L;

  static int instanceCount = 0;

  /**
   * The name of this logger
   */
  private String name;

  // The assigned levelInt of this logger. Can be null.
  private Level level;

  // The effective levelInt is the assigned levelInt and if null, a levelInt is
  // inherited form a parent.
  private Level effectiveLevel;

  /**
   * The parent of this category. All categories have at least one ancestor
   * which is the root category.
   */
  HLogger parent;

  /**
   * The children of this logger. A logger may have zero or more children.
   */
  Map<String, HLogger> childrenMap;

  /**
   * Array of appenders.
   */
  private ArrayList<Appender<ILoggingEvent>> appenderList;

  /**
   * Additivity is set to true by default, that is children inherit the
   * appenders of their ancestors by default. If this variable is set to
   * <code>false</code> then the appenders located in the ancestors of this
   * logger will not be used. However, the children of this logger will inherit
   * its appenders, unless the children have their additivity flag set to
   * <code>false</code> too. See the user manual for more details.
   */
  protected boolean additive = true;

  HLogger(String name, HLogger parent) {
    this.name = name;
    this.parent = parent;
    instanceCount++;
  }

  Level getEffectiveLevel() {
    return effectiveLevel;
  }

  Level getLevel() {
    return level;
  }

  public String getName() {
    return name;
  }

  private boolean isRootLogger() {
    // only the root logger has a null parent
    return parent == null;
  }

  /**
   * Get a child by its suffix.
   * 
   * <p>
   * IMPORTANT: Calls to this method must be within a syncronized block on this
   * logger!
   * 
   * @param suffix
   * @return
   */
  HLogger getChildBySuffix(final String suffix) {
    if (childrenMap == null) {
      return null;
    } else {
      return (HLogger) childrenMap.get(suffix);
    }
  }

  public synchronized void setLevel(Level newLevel) {
    if (level == newLevel) {
      // nothing to do;
      return;
    }

    level = newLevel;
    effectiveLevel = newLevel;
    if (childrenMap != null) {
      for (Iterator<HLogger> i = childrenMap.values().iterator(); i.hasNext();) {
        HLogger child = (HLogger) i.next();

        // tell child to handle parent levelInt change
        child.handleParentLevelChange(effectiveLevel);
      }
    }
  }

  /**
   * This method is invoked by parent logger to let this logger know that the
   * prent's levelInt changed.
   * 
   * @param newParentLevel
   */
  private synchronized void handleParentLevelChange(Level newParentLevel) {
    // changes in the parent levelInt affect children only if their levelInt is
    // null
    if (level == null) {
      effectiveLevel = newParentLevel;

      // propagate the parent levelInt change to this logger's children
      if (childrenMap != null) {
        for (Iterator<HLogger> i = childrenMap.values().iterator(); i.hasNext();) {
          HLogger child = (HLogger) i.next();
          // tell child to handle parent levelInt change
          child.handleParentLevelChange(effectiveLevel);
        }
      }
    }
  }

  /**
   * Remove all previously added appenders from this logger instance. <p/> This
   * is useful when re-reading configuration information.
   */
  public synchronized void removeAllAppenders() {
    if (appenderList != null) {
      int len = appenderList.size();
      for (int i = 0; i < len; i++) {
        Appender<ILoggingEvent> a = appenderList.get(i);
        a.stop();
      }
      appenderList.clear();
      appenderList = null;
    }
  }

  /**
   * Invoke all the appenders of this logger.
   * 
   * @param event
   *          The event to log
   */
  public void callAppenders(ILoggingEvent event) {
    for (HLogger l = this; l != null; l = l.parent) {
      // Protected against simultaneous call to addAppender, removeAppender,...
      synchronized (l) {
        if (l.appenderList != null) {
          l.appendLoopOnAppenders(event);
        }
        if (!l.additive) {
          break;
        }
      }
    }

    // No appenders in hierarchy, warn user only once.
    // if(!hierarchy.emittedNoAppenderWarning && writes == 0) {
    // LogLog.error("No appenders could be found for category (" +
    // this.getName() + ").");
    // LogLog.error("Please initialize the log4j system properly.");
    // hierarchy.emittedNoAppenderWarning = true;
    // }
  }

  private int appendLoopOnAppenders(ILoggingEvent event) {
    int size = 0;
    Appender<ILoggingEvent> appender;

    if (appenderList != null) {
      size = appenderList.size();
      for (int i = 0; i < size; i++) {
        appender = appenderList.get(i);
        appender.doAppend(event);
      }
    }
    return size;
  }

  /**
   * Remove the appender passed as parameter form the list of appenders.
   */
  public synchronized void removeAppender(Appender<ILoggingEvent> appender) {
    if ((appender != null) && (appenderList != null)) {
      appenderList.remove(appender);
    }
  }

  /**
   * Create a child of this logger by suffix, that is, the part of the name
   * extending this logger. For example, if this logger is named "x.y" and the
   * lastPart is "z", then the created child logger will be named "x.y.z".
   * 
   * <p>
   * IMPORTANT: Calls to this method must be within a syncronized block on this
   * logger.
   * 
   * @param lastPart
   *          the suffix (i.e. last part) of the child logger name. This
   *          parameter may not include dots, i.e. the logger separator
   *          character.
   * @return
   */
  HLogger createChildByLastNamePart(final String lastPart) {
    int i_index = lastPart.indexOf(CoreConstants.DOT);
    if (i_index != -1) {
      throw new IllegalArgumentException("Child name [" + lastPart
          + " passed as parameter, may not include [" + CoreConstants.DOT
          + "]");
    }

    if (childrenMap == null) {
      childrenMap = new HashMap<String, HLogger>(2);
    }
    HLogger childHLogger;
    if (this.isRootLogger()) {
      childHLogger = new HLogger(lastPart, this);
    } else {
      childHLogger = new HLogger(name + CoreConstants.DOT + lastPart,
          this);
    }
    childrenMap.put(lastPart, childHLogger);
    childHLogger.effectiveLevel = this.effectiveLevel;
    return childHLogger;
  }
  
  public final void trace(String msg) {
    if (effectiveLevel.levelInt <= Level.TRACE_INT) {
      throw new UnsupportedOperationException("not yet implemented");
    }
  }

  public void trace(String msg, Throwable t) {
    // To change body of implemented methods use File | Settings | File
    // Templates.
  }

  public void trace(Object parameterizedMsg, Object param1) {
    // To change body of implemented methods use File | Settings | File
    // Templates.
  }

  public void trace(String parameterizedMsg, Object param1, Object param2) {
    // To change body of implemented methods use File | Settings | File
    // Templates.
  }

  

  public final void debug(String msg) {
    if (effectiveLevel.levelInt <= Level.DEBUG_INT) {
      throw new UnsupportedOperationException("not yet implemented");
    }
  }

  public void debug(String msg, Throwable t) {
    // To change body of implemented methods use File | Settings | File
    // Templates.
  }

  public void debug(Object parameterizedMsg, Object param1) {
    // To change body of implemented methods use File | Settings | File
    // Templates.
  }

  public void debug(String parameterizedMsg, Object param1, Object param2) {
    // To change body of implemented methods use File | Settings | File
    // Templates.
  }

  public void error(String msg) {
    // To change body of implemented methods use File | Settings | File
    // Templates.
  }

  public void error(String msg, Throwable t) {
    // To change body of implemented methods use File | Settings | File
    // Templates.
  }

  public void error(String parameterizedMsg, Object param1) {
    // To change body of implemented methods use File | Settings | File
    // Templates.
  }

  public void error(String parameterizedMsg, Object param1, Object param2) {
    // To change body of implemented methods use File | Settings | File
    // Templates.
  }

  public void info(String msg) {
    // To change body of implemented methods use File | Settings | File
    // Templates.
  }

  public void info(String msg, Throwable t) {
    // To change body of implemented methods use File | Settings | File
    // Templates.
  }

  public void info(String parameterizedMsg, Object param1) {
    // To change body of implemented methods use File | Settings | File
    // Templates.
  }

  public void info(String parameterizedMsg, Object param1, Object param2) {
    // To change body of implemented methods use File | Settings | File
    // Templates.
  }

  public boolean isTraceEnabled() {
    return false; 
  }
  
  public boolean isDebugEnabled() {
    return false; 
  }

  public boolean isErrorEnabled() {
    return false; // To change body of implemented methods use File | Settings |
    // File Templates.
  }

  public boolean isInfoEnabled() {
    return false; // To change body of implemented methods use File | Settings |
    // File Templates.
  }

  public boolean isWarnEnabled() {
    return false; // To change body of implemented methods use File | Settings |
    // File Templates.
  }

  public void warn(String msg) {
    // To change body of implemented methods use File | Settings | File
    // Templates.
  }

  public void warn(String msg, Throwable t) {
    // To change body of implemented methods use File | Settings | File
    // Templates.
  }

  public void warn(String parameterizedMsg, Object param1) {
    // To change body of implemented methods use File | Settings | File
    // Templates.
  }

  public void warn(String parameterizedMsg, Object param1, Object param2) {
    // To change body of implemented methods use File | Settings | File
    // Templates.
  }

  public void trace(String format, Object arg) {
  }

  public void trace(String format, Object... argArray) {
  }
  
  public void debug(String format, Object arg) {
  }

  public void debug(String format, Object... argArray) {
  }

  public void info(String format, Object... argArray) {
  }

  public void warn(String format, Object... argArray) {
  }

  public void error(String format, Object... argArray) {
  }
}
